package com.chenjt.netty.eight_nio.selector.chat;

import io.netty.buffer.ByteBuf;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * 注释：聊天程序，多个客户端连接服务器端，一个客户端向服务器端发送消息，自己和其他客户端都能接收到消息
 *
 * @author chenjt
 * @date 2024/8/23 11:01
 */
public class NioServer {


    private static Map<String,SocketChannel> clientMap = new HashMap<>();


    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(8899));

        Selector selector = Selector.open();
        // 将serverSocketChannel注册到selector，serverSocketChannel关注OP_ACCEPT事件（接受连接事件），一个selector可以被多个serverSocketChannel注册
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);


        while (true) {

            // 会一直阻塞在这里，返回所关注的事件的数量
            selector.select();
            // 所关注事件的集合（serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);所以这里serverSocketChannel关注的事件是接受连接事件OP_ACCEPT）
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            selectionKeys.forEach(selectionKey -> {
                final SocketChannel client;

                try {
                    if(selectionKey.isAcceptable()){// 服务器端已经接受了客户端的连接
                        ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
                        client = server.accept();// 获取到客户端和服务器端连接的SocketChannel
                        client.configureBlocking(false);// 设置为非阻塞
                        // 将socketChannel注册到selector，socketChannel关注OP_READ事件（数据读取事件）
                        client.register(selector,SelectionKey.OP_READ);


                        String key = "[" + UUID.randomUUID().toString() + "]";
                        clientMap.put(key,client);

                    }else if(selectionKey.isReadable()){// 有数据可读
                        client = (SocketChannel)selectionKey.channel();
                        ByteBuffer readBuffer = ByteBuffer.allocate(1024);

                        int count = client.read(readBuffer);

                        if(count > 0){
                            readBuffer.flip();

                            Charset charset = Charset.forName("utf-8");
                            String receivedMessage = String.valueOf(charset.decode(readBuffer).array());

                            System.out.println(client + ":" + receivedMessage);

                            // 找到发送消息的客户端对应的key
                            String senderKey = null;
                            for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                if(client == entry.getValue()){
                                    senderKey = entry.getKey();
                                    break;
                                }
                            }


                            // 收到消息之后，发送数据给所有的客户端
                            for(Map.Entry<String,SocketChannel> entry : clientMap.entrySet()){
                                SocketChannel value = entry.getValue();

                                ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                                // 填充writeBuffer
                                writeBuffer.put((senderKey + ":" + receivedMessage).getBytes());
                                writeBuffer.flip();
                                // 将writeBuffer的内容写回客户端连接的channel中
                                value.write(writeBuffer);
                            }

                        }

                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }

            });

            selectionKeys.clear();// 这里一定要清空，否则下一次循环进来时，这些SelectionKey已经不存在了，会导致出现空指针异常

        }
    }

}
